// Author: Charlotte Walshaw (Charlotte.Walshaw@ed.ac.uk) & Andrew Gray
   Description: This script uses Sentinel-2 level-2A imagery and applies filtering, masking and classification 
   algorithms to identify green vegetation (vascular plants, bryophytes, green algae) and lichens across Antarctica.

// Imports
var REMA = ee.Image("UMN/PGC/REMA/V1_1/8m"), // Citation: Howat, I. M., Porter, C., Smith, B. E., Noh, M.-J., and Morin, P.: The Reference Elevation Model of Antarctica, The Cryosphere, 13, 665-674, 2019
    landonly = ee.FeatureCollection("users/andrewdotg/antarctica_land_only_simplified"),
    Rock_outcrop = ee.FeatureCollection("users/andrewdotg/VegetationSearchPolygonLandOnly"), // this layer was split into smaller individual polygons named geometry1, geometry2, geometry3, etc.... (example below)
    geometry1 = /* color: #0b4a8b */ee.Geometry.Polygon(
        [[[-54.034306497324586, -61.84852437413149],
          [-53.737675637949586, -61.03450577062931],
          [-55.638310403574586, -60.831681770186954],
          [-56.352421731699586, -61.55164395569406]]]), 
   
// Import Sentinel-2 Level-2A image collection
var S2SR = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED').filterBounds(geometry) 
      .filter(ee.Filter.calendarRange(2018, 2023, 'year'))
      .filter(ee.Filter.calendarRange(12, 2, 'month')) // Peak summer months
      .select(['B2', 'B3', 'B4', 'B5', 'B8', 'B11', 'SCL'])
      .filterMetadata('MEAN_SOLAR_ZENITH_ANGLE', 'less_than', 70);
var min_composite = S2SR.min();

// Clip to Antarctic coastline to remove ocean from analysis 
var clip = function(image){
  return image.clip(landonly)}; 
var clipped_coastline = S2SR.map(clip);

// Filter image collection by cloud cover over image tile
var cloud_filter = clipped_coastline.filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', 82); 
print('Image count:', cloud_filter.size()); // return the number of images
Map.addLayer(cloud_filter.min(), {bands: ['B4','B3','B2'], max: 1900}, 'S2 min mosaic - coastline clip');

// Elevation mask
var elevation_mask = function(image){
   var elevation = image.where((REMA.select('elevation').gt(500)), 0);
   return image.updateMask(elevation)};
var masked_elevation = cloud_filter.map(elevation_mask); 

// Snow & ice mask 
var AddNDSI = function(image){
  var NDSI = image.normalizedDifference(['B3', 'B11']).rename('NDSI');
  return image.addBands(NDSI)};
var masked_ndsi = masked_elevation.map(AddNDSI);

var snow_mask = function(image){
    var NDSI_mask = image.where((image.select('NDSI').gt(0.4)).or(image.select('SCL').eq(11)), 0);
    return image.updateMask(NDSI_mask)};
var masked_snow = masked_ndsi.map(snow_mask);

// Water mask
var AddNDWI = function(image){
  var NDWI = image.normalizedDifference(['B3', 'B8']).rename('NDWI');
  return image.addBands(NDWI)};
var addNDWI = masked_snow.map(AddNDWI);

var watermask = function(image){
  var water_mask = image.where((image.select('NDWI').gt(0.0)).or(image.select('SCL').eq(6)), 0); 
  return image.updateMask(water_mask)};
var masked_water = addNDWI.map(watermask);

// Topographic casted shadows ("Dark features/Shadows" for data before 2022-01-25) and cloud shadows
var darkfeaturecastedshadows = function(image){
  var dark_pixels = image.where((image.select('SCL').eq(2)).or(image.select('SCL').eq(3)), 0);
  return image.updateMask(dark_pixels)};
var masked_dark_pixels = masked_water.map(darkfeaturecastedshadows);

// Dark NIR features/pixels mask (moved from SCL 2 to SCL 7 in new processing baseline 4.00 for Jan 2022 imagery onwards, so need an additional dark feature mask for this imagery)
var dark_pixels = function(image){
  var dark_mask = image.where(image.select('B8').lt(0.15*1e4).multiply(image.select('SCL').neq(6)), 0); // From: https://developers.google.com/earth-engine/tutorials/community/sentinel-2-s2cloudless 
  return image.updateMask(dark_mask)};
var masked = masked_dark_pixels.map(dark_pixels);

// Cloud and saturated/defective mask - we do not use SCL 7 (masks a lot of bare ground)
var cloudmask = function(image){ 
  var cloud_mask = image.where((image.select('SCL').eq(1)).or(image.select('SCL').eq(8))
  .or(image.select('SCL').eq(9)).or(image.select('SCL').eq(10)), 0);
  return image.updateMask(cloud_mask).divide(10000)};
var masked_cloud = masked.map(cloudmask);

// Add spectral reflectance index bands for targeted vegetation detection   
var AddIndices = function(image){ 
  var NDVI = image.normalizedDifference(['B8', 'B4']).rename('NDVI'); // 10 m resolution
  var NDMI = image.normalizedDifference(['B8', 'B11']).rename('NDMI'); // 20 & 10 m resolution
  var GRVI = image.normalizedDifference(['B3', 'B4']).rename('GRVI'); // 10 m resolution, helps to remove iron-containing (red) rock
  var NDRE = image.normalizedDifference(['B8', 'B5']).rename('NDRE'); // B8 is 10m resolution, B5 is 20 m resolutions
  return image.addBands(NDMI).addBands(NDVI).addBands(GRVI).addBands(NDRE)};
var indices = masked_cloud.map(AddIndices);

// Green vegetation detection thresholding (vascular plants, bryophytes, green algae)
var green_veg = function(image){
  var veg_indices = image.select('NDVI').gt(0.4)
  .and(image.select('NDVI').lt(0.98))
  .and(image.select('NDMI').gt(-0.2)) 
  .and(image.select('NDMI').lt(0.6));
  return image.updateMask(veg_indices)};
var greenVeg = indices.map(green_veg); 

// Add green layer as a band, so that it can be masked before detecting lichens (to prevent any instances of overlap between green veg and lichen pixels)
var GreenVegBand = function(image){
  var band = image.select('NDVI').gt(0.4)
  .and(image.select('NDVI').lt(0.98))
  .and(image.select('NDMI').gt(-0.2))
  .and(image.select('NDMI').lt(0.6)).rename('GreenVegMask').toFloat(); // 'GreenVegMask' is the new band applied to all images for the green veg layer
  return image.addBands(band)};	
var GreenVegMask1 = indices.map(GreenVegBand); // Green vegetation is detected first because pixel NDVI values which can reach values > 0.4 are more likely to be green vegetation

// The new 'GreenVegMask' band is binary (green veg = 1, everything else = 0). To prevent 'everything else' from being masked when masking the green veg, remove the 0 from the GreenVegMask band 
var RemoveZero = function(image){ 
  var masking = image.select('GreenVegMask').add(4).rename('GreenVegMask_1').toFloat();
  return image.addBands(masking)};
var GreenVegMask = GreenVegMask1.map(RemoveZero); 

// Mask the green vegetation layer using a mean composite (the latter harmonizes all green veg mask band values for pixels detected at least once as green vegetation) 
var mean = GreenVegMask.reduce(ee.Reducer.mean()); // code is looking through all temporal images before masking green vegetation - so had chance to detect under variability 
  
// Lichen detection thresholding 
var lichen_veg = function(image){
  var lichen_indices = image.select('NDVI').gt(0.2) 
  .and(mean.select('GreenVegMask_1_mean').eq(4)) // keeps non green veg ('everything else') in analysis whilst masking the green veg - pixels which have been detected as green veg, even if only once, will have mean values other than 4 or 0, so they will be masked
  .and(mean.select('GreenVegMask_mean').eq(0)) // "^"  pixels which are always 4 or 0 (i.e. never detected as green veg) will not be masked
  .and(image.select('NDVI').lt(0.4)) 
  .and(image.select('NDMI').gt(-0.2)) 
  .and(image.select('NDMI').lt(-0.05)) 
  .and(image.select('NDRE').gt(0.1)) 
  .and(image.select('GRVI').gt(-0.05));
  return image.updateMask(lichen_indices)};
var lichen = GreenVegMask.map(lichen_veg);

// create mean composite map layers for lichens and green veg
var composite_green = greenVeg.reduce(ee.Reducer.mean()); 
var composite_lichen = lichen.reduce(ee.Reducer.mean());
Map.addLayer(composite_green, {'bands': ['B4_mean'], 'palette': ['green']}, 'Green vegetation composite'); 
Map.addLayer(composite_lichen, {'bands': ['B4_mean'], 'palette': ['#feb24c']}, 'Lichens composite');

// Export lichen layer as tiffs to google drive (for further analysis in QGIS)
// Export.image.toDrive({
//   'image': composite_lichen,
//   'description': 'Lichens_Antarctica_geometry',
//   'crs': 'EPSG:3031',
//   'scale': 10,
//   'region': geometry,
//   'maxPixels': 1e13,
//   'skipEmptyTiles': true});

// Export green veg layer 
// Export.image.toDrive({
//   image: composite_green,
//   description: 'GreenVeg_Antarctica_geometry',
//   crs: 'EPSG:3031',
//   scale: 10,
//   region: geometry,
//   maxPixels: 1e13,
//   skipEmptyTiles: true})

